package com.balloon.springboot.quartz.service.impl;

import com.balloon.core.exception.BusinessRuntimeException;
import com.balloon.springboot.core.rules.DatePageVO;
import com.balloon.springboot.core.utils.BeanCopyUtil;
import com.balloon.springboot.quartz.dao.JobDao;
import com.balloon.springboot.quartz.dto.QuartzJobDto;
import com.balloon.springboot.quartz.entity.QuartzJob;
import com.balloon.springboot.quartz.enums.JobStatusEnums;
import com.balloon.springboot.quartz.enums.exception.QuartzExceptionEnums;
import com.balloon.springboot.quartz.service.JobService;
import com.balloon.springboot.quartz.vo.QuartzJobVo;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;

/**
 * @author liaofuxing
 */
@Service
public class JobServiceImpl implements JobService {

    private final static Logger logger = LoggerFactory.getLogger(JobServiceImpl.class);

    private static final String TRIGGER_IDENTITY = "trigger";

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private JobDao jobDao;

    /**
     * 分页查询
     *
     * @param quartzJobDto 任务Dto
     * @return DatePageVO<QuartzJobVo>
     */
    @Override
    public DatePageVO<QuartzJobVo> quartzJobPage(QuartzJobDto quartzJobDto) {

        Pageable pageable = PageRequest.of(quartzJobDto.getPage() - 1, quartzJobDto.getPageSize(), Sort.Direction.ASC, "id");

        Specification<QuartzJob> specification = (root, criteriaQuery, criteriaBuilder) -> {
            //分页条件组装
            List<Predicate> list = new ArrayList<>();
            if (!StringUtils.isEmpty(quartzJobDto.getJobName())) {
                list.add(criteriaBuilder.like(root.get("jobName").as(String.class), "%" + quartzJobDto.getJobName() + "%"));
            }

            if (!StringUtils.isEmpty(quartzJobDto.getJobGroup())) {
                list.add(criteriaBuilder.equal(root.get("jobGroup").as(String.class), quartzJobDto.getJobGroup()));
            }
            return criteriaBuilder.and(list.toArray(new Predicate[0]));
        };
        Page<QuartzJob> quartzJobPage = jobDao.findAll(specification, pageable);
        List<QuartzJobVo> quartzJobVoList = BeanCopyUtil.copyListProperties(quartzJobPage.getContent(), QuartzJobVo::new);
        return new DatePageVO<>(quartzJobPage.getTotalElements(), quartzJobVoList);
    }


    @Override
    public List<QuartzJob> listJob() {
        return jobDao.findAll();
    }

    /**
     * 保存任务
     *
     * @param quartzJobDto Job 对象
     */
    @Override
    public void saveJob(QuartzJobDto quartzJobDto) {
        try {
            QuartzJob quartz = BeanCopyUtil.copyProperties(quartzJobDto, QuartzJob::new);

            schedulerJob(quartz);

            quartz.setTriggerState(JobStatusEnums.RUNNING.getStatus());
            jobDao.save(quartz);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessRuntimeException(QuartzExceptionEnums.SAVE_JOB_ERROR);
        }
    }

    /**
     * 触发任务
     *
     * @param jobName  任务名称
     * @param jobGroup 任务分组
     */
    @Override
    public void triggerJob(String jobName, String jobGroup) {
        try {
            JobKey key = new JobKey(jobName, jobGroup);
            scheduler.triggerJob(key);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessRuntimeException(QuartzExceptionEnums.TRIGGER_JOB_ERROR);
        }
    }

    /**
     * 停止或恢复任务
     *
     * @param quartzJobDto 任务Dto
     * @return ResultVO<Object>
     */
    @Override
    public void runOrStopJob(QuartzJobDto quartzJobDto) {
        if (JobStatusEnums.RUNNING.getStatus().equals(quartzJobDto.getTriggerState())) {
            logger.info("停止任务.....");
            pauseJob(quartzJobDto.getJobName(), quartzJobDto.getJobGroup());
        } else {
            logger.info("恢复任务.....");
            resumeJob(quartzJobDto.getJobName(), quartzJobDto.getJobGroup());
        }
    }

    @Override
    public void pauseJob(String jobName, String jobGroup) {
        try {
            JobKey key = new JobKey(jobName, jobGroup);
            scheduler.pauseJob(key);
            QuartzJob byJobName = jobDao.findByJobName(jobName);
            byJobName.setTriggerState(JobStatusEnums.PAUSED.getStatus());
            jobDao.save(byJobName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessRuntimeException(QuartzExceptionEnums.PAUSE_JOB_ERROR);
        }
    }

    @Override
    public void resumeJob(String jobName, String jobGroup) {
        try {
            JobKey key = new JobKey(jobName, jobGroup);
            scheduler.resumeJob(key);
            QuartzJob byJobName = jobDao.findByJobName(jobName);
            byJobName.setTriggerState(JobStatusEnums.RUNNING.getStatus());
            jobDao.save(byJobName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessRuntimeException(QuartzExceptionEnums.RESUME_JOB_ERROR);
        }
    }

    @Override
    @Transactional
    @Modifying
    public void removeJob(String jobName, String jobGroup) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_IDENTITY + jobName, jobGroup);
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
            jobDao.deleteByJobName(jobName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessRuntimeException(QuartzExceptionEnums.REMOVE_JOB_ERROR);
        }
    }

    @Override
    public QuartzJob getJob(String jobName, String jobGroup) {
        return jobDao.findByJobName(jobName);
    }

    @Override
    @Transactional
    public void updateJob(QuartzJobDto quartzJobDto) {
        try {
            // 更新 job, 先调用删除job
            removeJob(quartzJobDto.getOldJobName(), quartzJobDto.getOldJobGroup());
            QuartzJob quartz = BeanCopyUtil.copyProperties(quartzJobDto, QuartzJob::new);
            schedulerJob(quartz);
            jobDao.save(quartz);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessRuntimeException(QuartzExceptionEnums.UPDATE_JOB_ERROR);
        }
    }

    @Override
    public void schedulerJob(QuartzJob job) throws Exception {
        //构建job信息
        Class cls = Class.forName(job.getJobClassName());
        //cls.newInstance(); // 检验类是否存在
        JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(job.getJobName(), job.getJobGroup())
                .withDescription(job.getDescription()).build();

        // 触发时间点
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression().trim());
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(TRIGGER_IDENTITY + job.getJobName(), job.getJobGroup())
                .startNow().withSchedule(cronScheduleBuilder).build();

        // 把作业和触发器注册到任务调度中
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 校验用户名不重复
     *
     * @param jobName 任务名
     */
    public Boolean validateJobNameRepeat(String jobName, Integer id) {
        QuartzJob byJobName = jobDao.findByJobNameAndIdNot(jobName, id);
        return byJobName != null;
    }
}
